/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.csp.sentinel.annotation.aspectj;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

import java.lang.reflect.Method;

/**
 * Aspect for methods with {@link SentinelResource} annotation.
 *
 * @author Eric Zhao
 */
@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {

    //判断是否存在@SentinelResuorce注解
    @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
    public void sentinelResourceAnnotationPointcut() {
    }

    @Around("sentinelResourceAnnotationPointcut()")
    public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
        Method originMethod = resolveMethod(pjp);//获取调用目标方法
        //获取目标方法上SentinelResource注解的属性信息
        SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
        if (annotation == null) {//判断是否为空
            // Should not go through here. 为空，抛出异常
            throw new IllegalStateException("Wrong state for SentinelResource annotation");
        }//获取资源名，如果注解中有配置value值，这个值就是资源名，如果没有配置，就通过类名、方法、参数类型来构建资源名
        String resourceName = getResourceName(annotation.value(), originMethod);
        //获取当前进入类型，如果是SentineResource注解的方法，默认为EntryType.OUT类型.
        // 如果是拦截器中则会使用EntryType.IN类型
        EntryType entryType = annotation.entryType();
        //获取资源类型。如果是SentineResource注解的方法默认为ResourceTypeConstants.COMMON为0,
        // 如果是拦截器中则会使用ResourceTypeConstants.COMMON_WEB为1
        int resourceType = annotation.resourceType();
        Entry entry = null;
        try {//开始进行流程等规则校验
            entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
            Object result = pjp.proceed();//调用目标方法
            return result;//返回结果
        } catch (BlockException ex) {//如果抛出，规则校验异常，表示不通过，则调用SentinelResource注解配置的处理方法
            return handleBlockException(pjp, annotation, ex);
        } catch (Throwable ex) {//如果抛出其他异常，表示是业务抛出的异常
            Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();//获取SentinelResource注解中配置的忽视异常类型
            // The ignore list will be checked first. //判断当前抛出的业务异常，属于注解中配置的忽视异常类型，则直接往外抛出当前异常
            if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
                throw ex;
            }//判断当前抛出的业务异常，是否属于注解中配置的忽视异常类型
            if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
                //属于配置的忽视异常
                traceException(ex);//日志捕捉异常信息
                return handleFallback(pjp, annotation, ex);//调用SentinelResource注解中配置的失败回调方法，来处理异常。
            }
            // No fallback function can handle the exception, so throw it out.  不存在回调方法去处理异常，就往外抛出异常
            throw ex;
        } finally {
            if (entry != null) {//判断是否调用过规则校验的责任链
                entry.exit(1, pjp.getArgs());//调用校验规则责任链的退出方法
            }
        }
    }
}
